import FBoxCollider from "./FBoxCollider";
import FCircleCollider from "./FCircleCollider";
import FCollider, { ColliderType, StateType } from "./FCollider";
import FPolygonCollider from "./FPolygonCollider";
import { Intersection } from "./Intersection";
import { QuadTree } from "./QuadTree";

let tempVec2 = cc.v2();
let tempMat4 = cc.mat4();
let tempArr: any[] = [];
let collisionState: number = 0;
function obbApplyMatrix(rect: cc.Rect, mat4: cc.Mat4, out_bl: cc.Vec2, out_tl: cc.Vec2, out_tr: cc.Vec2, out_br: cc.Vec2) {
    let x = rect.x;
    let y = rect.y;
    let width = rect.width;
    let height = rect.height;

    let mat4m = mat4.m;
    let m00 = mat4m[0], m01 = mat4m[1], m04 = mat4m[4], m05 = mat4m[5];
    let m12 = mat4m[12], m13 = mat4m[13];

    let tx = m00 * x + m04 * y + m12;
    let ty = m01 * x + m05 * y + m13;
    let xa = m00 * width;
    let xb = m01 * width;
    let yc = m04 * height;
    let yd = m05 * height;

    out_tl.x = tx;
    out_tl.y = ty;
    out_tr.x = xa + tx;
    out_tr.y = xb + ty;
    out_bl.x = yc + tx;
    out_bl.y = yd + ty;
    out_br.x = xa + yc + tx;
    out_br.y = xb + yd + ty;
}

enum CollisionType {
    onEnter = "onCollisionEnter",
    onStay = "onCollisionStay",
    onExit = "onCollisionExit"
}
export default class FColliderManager {
    private static _instance: FColliderManager = null;

    public static get instance() {
        if (!this._instance) {
            this._instance = new FColliderManager();
        }
        return this._instance;
    };

    private _tree: QuadTree<FCollider> = null;
    private constructor() {
        this._tree = new QuadTree<FCollider>(this._treeRect, 0, this._maxDepth, this._maxChildren);
    }
    //是否要重新建树
    private _treeDirty: boolean = true;

    private _maxDepth: number = 4;
    public get maxDepth(): number {
        return this._maxDepth;
    }
    public set maxDepth(value: number) {
        if (value != this._maxDepth) {
            this._maxDepth = value;
            this._treeDirty = true;
        }
    }

    private _maxChildren: number = 10;
    public get maxChildren(): number {
        return this._maxChildren;
    }
    public set maxChildren(value: number) {
        if (this._maxChildren != value) {
            this._maxChildren = value;
            this._treeDirty = true;
        }
    }

    private _treeRect: cc.Rect = cc.rect(0, 0, cc.winSize.width, cc.winSize.height);
    public get treeRect(): cc.Rect {
        return this._treeRect;
    }
    //设置四叉树大小
    public set treeRect(value: cc.Rect) {
        if (this._treeRect) {
            if (this._treeRect.equals(value)) return;
        }
        this._treeRect.set(value);
        this._treeDirty = false;
    }

    private _enable: boolean = false;
    public get enable(): boolean {
        return this._enable;
    }
    public set enable(value: boolean) {
        this._enable = value;
        if (value) {
            cc.director.getScheduler().enableForTarget(this);
            cc.director.getScheduler().scheduleUpdate(this, cc.Scheduler.PRIORITY_NON_SYSTEM, false);
        }
    }

    private _colliders: FCollider[] = [];
    public addCollider(collider: FCollider) {
        let colliders = this._colliders;
        this.initCollider(collider);
        colliders.push(collider);
    }
    public removeCollider(collider: FCollider) {
        for (let i = this._colliders.length - 1; i >= 0; i--) {
            let c = this._colliders[i];
            if (collider.colliderId === c.colliderId) {
                this._colliders.splice(i, 1);
                let map = collider.contactMap;
                if (map) {
                    map.forEach((value, key) => {
                        this.updateCollideExit(collider, value.other);
                    });
                }
                break;
            }
        }
    }

    public initCollider(collider: FCollider) {
        collider.initCollider();
    }

    public updateCollider(c: FCollider) {
        c.node.getWorldMatrix(tempMat4);
        if (c.type === ColliderType.Box) {
            let collider = c as FBoxCollider;
            let size = collider.size;
            collider.aabb.x = collider.offset.x - size.width / 2;
            collider.aabb.y = collider.offset.y - size.height / 2;
            collider.aabb.width = size.width;
            collider.aabb.height = size.height;
            let wps = collider.worldPoints;
            let wp0 = wps[0], wp1 = wps[1],
                wp2 = wps[2], wp3 = wps[3];
            obbApplyMatrix(collider.aabb, tempMat4, wp0, wp1, wp2, wp3);
            let minx = Math.min(wp0.x, wp1.x, wp2.x, wp3.x);
            let miny = Math.min(wp0.y, wp1.y, wp2.y, wp3.y);
            let maxx = Math.max(wp0.x, wp1.x, wp2.x, wp3.x);
            let maxy = Math.max(wp0.y, wp1.y, wp2.y, wp3.y);
            let worldEdge = collider.worldEdge;
            for (let i = 0, l = wps.length; i < l; i++) {
                if (!worldEdge[i]) worldEdge[i] = cc.v2();
                cc.Vec2.subtract(worldEdge[i], wps[(i + 1) % l], wps[i]);
            }
            collider.aabb.x = minx;
            collider.aabb.y = miny;
            collider.aabb.width = maxx - minx;
            collider.aabb.height = maxy - miny;
        } else if (c.type == ColliderType.Circle) {
            let collider = c as FCircleCollider;
            cc.Vec2.transformMat4(tempVec2, collider.offset, tempMat4);
            collider.worldPosition.x = tempVec2.x;
            collider.worldPosition.y = tempVec2.y;
            let mm = tempMat4.m;
            let tempx = mm[12], tempy = mm[13];
            mm[12] = mm[13] = 0;

            tempVec2.x = collider.radius;
            tempVec2.y = 0;

            cc.Vec2.transformMat4(tempVec2, tempVec2, tempMat4);
            let d = Math.sqrt(tempVec2.x * tempVec2.x + tempVec2.y * tempVec2.y);

            collider.worldRadius = d;

            collider.aabb.x = collider.worldPosition.x - d;
            collider.aabb.y = collider.worldPosition.y - d;
            collider.aabb.width = d * 2;
            collider.aabb.height = d * 2;

            mm[12] = tempx;
            mm[13] = tempy;
        } else if (c.type == ColliderType.Polygon) {
            let collider = c as FPolygonCollider;
            let points = collider.points;
            let worldPoints = collider.worldPoints;
            let worldEdge = collider.worldEdge;
            worldPoints.length = points.length;

            let minx = Number.MAX_SAFE_INTEGER, miny = Number.MAX_SAFE_INTEGER, maxx = -Number.MAX_SAFE_INTEGER, maxy = -Number.MAX_SAFE_INTEGER;
            for (let i = 0, l = points.length; i < l; i++) {
                if (!worldPoints[i]) {
                    worldPoints[i] = cc.v2();
                }
                tempVec2.x = points[i].x + collider.offset.x;
                tempVec2.y = points[i].y + collider.offset.y;
                cc.Vec2.transformMat4(tempVec2, tempVec2, tempMat4);

                let x = tempVec2.x;
                let y = tempVec2.y;

                worldPoints[i].set(tempVec2);

                if (x > maxx) maxx = x;
                if (x < minx) minx = x;
                if (y > maxy) maxy = y;
                if (y < miny) miny = y;
            }
            if (c.isConvex) {
                for (let i = 0, l = worldPoints.length; i < l; i++) {
                    if (!worldEdge[i]) worldEdge[i] = cc.v2();
                    cc.Vec2.subtract(worldEdge[i], worldPoints[(i + 1) % l], worldPoints[i]);
                }
            }

            collider.aabb.x = minx;
            collider.aabb.y = miny;
            collider.aabb.width = maxx - minx;
            collider.aabb.height = maxy - miny;
        }
    }

    public shouldCollide(c1: FCollider, c2: FCollider) {
        let node1 = c1.node, node2 = c2.node;
        //@ts-ignore
        let collisionMatrix = cc.game.collisionMatrix;
        return node1 !== node2 && collisionMatrix[node1.groupIndex][node2.groupIndex];
    }

    public update(dt: number) {
        if (!this.enable) return;
        // console.time("碰撞");
        this.oneTest(dt);
        // console.timeEnd("碰撞");
    }

    /**
     * 1.清空四叉树
     * 2.更新Collider
     * 3.插入四叉树
     * 4.筛选重复碰撞组(暂无)
     * 5.碰撞检测
     * @param dt 
     * @returns 
     */
    public oneTest(dt: number) {
        if (this._treeDirty) {
            this._tree = new QuadTree<FCollider>(this._treeRect, 0, this._maxDepth, this._maxChildren);
            this._treeDirty = false;
        }
        this._tree.clear();
        for (let i = this._colliders.length - 1; i >= 0; i--) {
            let collider = this._colliders[i];
            if (!collider || !collider.isValid) {
                this._colliders.splice(i, 1);
                continue;
            }
            collider.contactMap.forEach((value) => {
                value.state = StateType.NoTest;
            });
            this.updateCollider(this._colliders[i]);
            this._tree.insert(this._colliders[i]);
        }

        tempArr.length = 0;
        this._tree.getAllNeedTestColliders(tempArr);
        for (let k = 0, klen = tempArr.length; k < klen; k++) {
            let _colliders = tempArr[k];
            for (let i = 0, len = _colliders.length; i < len; i++) {
                let icollider = _colliders[i];
                for (let j = i + 1; j < len; j++) {
                    let jcollider = _colliders[j];
                    if (!this.shouldCollide(icollider, jcollider)) continue;
                    switch (icollider.type) {
                        case ColliderType.Circle:
                            if (jcollider.type === ColliderType.Circle) {
                                collisionState = Intersection.circleCircle(icollider as FCircleCollider, jcollider as FCircleCollider) ? 1 : 0;
                            } else if (jcollider.type === ColliderType.Box || jcollider.type === ColliderType.Polygon) {
                                collisionState = Intersection.polygonCircle((jcollider as FPolygonCollider).worldPoints, icollider as FCircleCollider) ? 1 : 0;
                            }
                            break;
                        case ColliderType.Box:
                            if (jcollider.type === ColliderType.Circle) {
                                collisionState = Intersection.polygonCircle((icollider as FPolygonCollider).worldPoints, jcollider as FCircleCollider) ? 1 : 0;
                            } else if (jcollider.type === ColliderType.Box) {
                                if (icollider.node.angle === 0 && jcollider.node.angle === 0) {
                                    collisionState = Intersection.rectRect(icollider.aabb, jcollider.aabb) ? 1 : 0;
                                } else {
                                    collisionState = Intersection.satPolygonPolygon((icollider as FPolygonCollider).worldPoints, (jcollider as FPolygonCollider).worldPoints, (icollider as FPolygonCollider).worldEdge, (jcollider as FPolygonCollider).worldEdge) ? 1 : 0;
                                }
                            }
                            else if (jcollider.type === ColliderType.Polygon) {
                                if (!jcollider.isConvex) {
                                    collisionState = Intersection.polygonPolygon((icollider as FPolygonCollider).worldPoints, (jcollider as FPolygonCollider).worldPoints) ? 1 : 0;
                                }
                                else {
                                    collisionState = Intersection.satPolygonPolygon((icollider as FPolygonCollider).worldPoints, (jcollider as FPolygonCollider).worldPoints, (icollider as FPolygonCollider).worldEdge, (jcollider as FPolygonCollider).worldEdge) ? 1 : 0;
                                }
                            }
                            break;
                        case ColliderType.Polygon:
                            if (jcollider.type === ColliderType.Circle) {
                                collisionState = Intersection.polygonCircle((icollider as FPolygonCollider).worldPoints, jcollider as FCircleCollider) ? 1 : 0;
                            } else if (icollider.isConvex && jcollider.isConvex) {
                                collisionState = Intersection.satPolygonPolygon((icollider as FPolygonCollider).worldPoints, (jcollider as FPolygonCollider).worldPoints, (icollider as FPolygonCollider).worldEdge, (jcollider as FPolygonCollider).worldEdge) ? 1 : 0;
                            } else {
                                collisionState = Intersection.polygonPolygon((icollider as FPolygonCollider).worldPoints, (jcollider as FPolygonCollider).worldPoints) ? 1 : 0;
                            }
                            break;
                    }
                    if (collisionState == 1) {
                        this.updateCollideContact(icollider, jcollider);
                    } else {
                        this.updateCollideExit(icollider, jcollider);
                    }
                }
            }
        }
        for (let i = this._colliders.length - 1; i >= 0; i--) {
            let collider = this._colliders[i];
            collider.contactMap.forEach((value) => {
                if (value.state === StateType.NoTest) {
                    this.updateCollideExit(collider, value.other);
                }
            });
        }
        this.drawColliders();
        this.drawQuadTree();
        // console.timeEnd("四叉树碰撞耗时");
    }

    /**
     * 获得当前与collider碰撞的相关碰撞体
     *
     * @param {FCollider} icollider
     * @memberof FColliderManager
     */
    private checkCollider(icollider: FCollider, out?: FCollider[]) {
        out = out || [];
        let colliderslist = [];
        this._tree.retrieve(icollider, colliderslist);
        for (let j = 0, jlen = colliderslist.length; j < jlen; j++) {
            let jcollider = colliderslist[j];
            if (jcollider.colliderId === icollider.colliderId) continue;
            if (!this.shouldCollide(icollider, jcollider)) continue;
            switch (icollider.type) {
                case ColliderType.Circle:
                    if (jcollider.type === ColliderType.Circle) {
                        if (Intersection.circleCircle(icollider as FCircleCollider, jcollider as FCircleCollider)) {
                            out.push(jcollider);
                        }
                    } else if (jcollider.type === ColliderType.Box || jcollider.type === ColliderType.Polygon) {
                        if (Intersection.polygonCircle((jcollider as FPolygonCollider).worldPoints, icollider as FCircleCollider)) {
                            out.push(jcollider);
                        }
                    }
                    break;
                case ColliderType.Box:
                    if (jcollider.type === ColliderType.Circle) {
                        if (Intersection.polygonCircle((icollider as FPolygonCollider).worldPoints, jcollider as FCircleCollider)) {
                            out.push(jcollider);
                        }
                    } else if (jcollider.type === ColliderType.Box) {
                        if (icollider.node.angle === 0 && jcollider.node.angle === 0) {
                            if (Intersection.rectRect(icollider.aabb, jcollider.aabb)) {
                                out.push(jcollider);
                            }
                        } else {
                            if (Intersection.satPolygonPolygon((icollider as FPolygonCollider).worldPoints, (jcollider as FPolygonCollider).worldPoints, (icollider as FPolygonCollider).worldEdge, (jcollider as FPolygonCollider).worldEdge)) {
                                out.push(jcollider);
                            }
                        }
                    }
                    else if (jcollider.type === ColliderType.Polygon) {
                        if (!jcollider.isConvex) {
                            if (Intersection.polygonPolygon((icollider as FPolygonCollider).worldPoints, (jcollider as FPolygonCollider).worldPoints)) {
                                out.push(jcollider);
                            }
                        }
                        else {
                            if (Intersection.satPolygonPolygon((icollider as FPolygonCollider).worldPoints, (jcollider as FPolygonCollider).worldPoints, (icollider as FPolygonCollider).worldEdge, (jcollider as FPolygonCollider).worldEdge)) {
                                out.push(jcollider);
                            }
                        }
                    }
                    break;
                case ColliderType.Polygon:
                    if (jcollider.type === ColliderType.Circle) {
                        if (Intersection.polygonCircle((icollider as FPolygonCollider).worldPoints, jcollider as FCircleCollider)) {
                            out.push(jcollider);
                        }
                    } else if (icollider.isConvex && jcollider.isConvex) {
                        if (Intersection.satPolygonPolygon((icollider as FPolygonCollider).worldPoints, (jcollider as FPolygonCollider).worldPoints, (icollider as FPolygonCollider).worldEdge, (jcollider as FPolygonCollider).worldEdge)) {
                            out.push(jcollider);
                        }
                    } else {
                        if (Intersection.polygonPolygon((icollider as FPolygonCollider).worldPoints, (jcollider as FPolygonCollider).worldPoints)) {
                            out.push(jcollider);
                        }
                    }
                    break;
            }
        }
        return out;
    }

    /**
     *更新两个碰撞体之前的碰撞信息,延迟到发生碰撞才创建
     *
     * @private
     * @param {*} collider1
     * @param {*} collider2
     * @memberof _ColliderManager
     */
    private updateCollideContact(collider1: FCollider, collider2: FCollider) {
        let a = collider1.contactMap.get(collider2.colliderId);
        if (!a) {
            collider1.contactMap.set(collider2.colliderId, { other: collider2, state: StateType.IsTest });
            this._doCollide(collider1, collider2, CollisionType.onEnter);
        } else {
            a.state = StateType.IsTest;
            this._doCollide(collider1, collider2, CollisionType.onStay);
        }
        let b = collider2.contactMap.get(collider1.colliderId);
        if (!b) {
            collider2.contactMap.set(collider1.colliderId, { other: collider1, state: StateType.IsTest });
            this._doCollide(collider2, collider1, CollisionType.onEnter);
        } else {
            b.state = StateType.IsTest;
            this._doCollide(collider2, collider1, CollisionType.onStay);
        }
    }

    private updateCollideExit(collider1: FCollider, collider2: FCollider) {
        let a = collider1.contactMap.delete(collider2.colliderId);
        if (a) {
            this._doCollide(collider1, collider2, CollisionType.onExit);
        }
        let b = collider2.contactMap.delete(collider1.colliderId);
        if (b) {
            this._doCollide(collider2, collider1, CollisionType.onExit);
        }
    }

    private _doCollide(collider1: FCollider, collider2: FCollider, type: CollisionType) {
        //@ts-ignore
        let comps1 = collider1.node._components;
        let comp;
        for (let i = 0, l = comps1.length; i < l; i++) {
            comp = comps1[i];
            if (comp[type]) {
                comp[type](collider2, collider1);
            }
        }
    }

    private _enableDebugDraw: boolean = false;
    public get enableDebugDraw(): boolean {
        return this._enableDebugDraw;
    }
    public set enableDebugDraw(value: boolean) {
        if (value && !this._enableDebugDraw) {
            this._checkDebugDrawValid();
            this._debugDrawer.node.active = true;
        }
        else if (!value && this._enableDebugDraw) {
            this._debugDrawer.clear(true);
            this._debugDrawer.node.active = false;
        }
        this._enableDebugDraw = value;
    }

    private _enableQuadTreeDraw: boolean = false;
    public get enableQuadTreeDraw(): boolean {
        return this._enableQuadTreeDraw;
    }
    public set enableQuadTreeDraw(value: boolean) {
        if (value && !this._enableQuadTreeDraw) {
            this._checkDebugDrawValid();
            this._debugDrawer.node.active = true;
        }
        else if (!value && this._enableQuadTreeDraw) {
            this._debugDrawer.clear(true);
            this._debugDrawer.node.active = false;
        }
        this._enableQuadTreeDraw = value;
    }

    private _debugDrawer: cc.Graphics = null;
    private _checkDebugDrawValid() {
        if (!this._debugDrawer || !this._debugDrawer.isValid) {
            let node = new cc.Node('FCOLLISION_MANAGER_DEBUG_DRAW');
            node.zIndex = cc.macro.MAX_ZINDEX;
            cc.game.addPersistRootNode(node);
            this._debugDrawer = node.addComponent(cc.Graphics);
            this._debugDrawer.lineWidth = 5;
        }
    }
    drawColliders() {
        if (!this._enableDebugDraw) {
            return;
        }
        this._checkDebugDrawValid();

        let debugDrawer = this._debugDrawer;
        debugDrawer.clear();

        let colliders = this._colliders;

        for (let i = 0, l = colliders.length; i < l; i++) {
            let collider = colliders[i];
            debugDrawer.strokeColor = cc.Color.RED;
            if (collider.type === ColliderType.Box || collider.type === ColliderType.Polygon) {
                //@ts-ignore
                let ps = collider.worldPoints;
                if (ps.length > 0) {
                    cc.Vec2.set(tempVec2, ps[0].x, ps[0].y);
                    debugDrawer.moveTo(tempVec2.x, tempVec2.y);
                    for (let j = 1; j < ps.length; j++) {
                        cc.Vec2.set(tempVec2, ps[j].x, ps[j].y);
                        debugDrawer.lineTo(tempVec2.x, tempVec2.y);
                    }
                    debugDrawer.close();
                    debugDrawer.stroke();
                }
            }
            else if (collider.type === ColliderType.Circle) {
                //@ts-ignore
                debugDrawer.circle(collider.worldPosition.x, collider.worldPosition.y, collider.worldRadius);
                // console.log(collider.worldPosition.toString(),collider.worldRadius)
                debugDrawer.stroke();
            }
        }
    }

    drawQuadTree() {
        if (!this._enableQuadTreeDraw) {
            return;
        }
        this._checkDebugDrawValid();

        let debugDrawer = this._debugDrawer;
        if (!this._enableDebugDraw) debugDrawer.clear(true);
        this._tree.render(debugDrawer);
    }
}